package com.coincalf.wallet.task.withdraw;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.coincalf.dto.CoinInfoDto;
import com.coincalf.feign.CoinServiceClient;
import com.coincalf.framework.dto.CoinWithdrawDTO;
import com.coincalf.framework.dto.WithdrawResultDTO;
import com.coincalf.framework.enums.CoinWithdrawStatus;
import com.coincalf.framework.enums.TxStateEnum;
import com.coincalf.framework.exception.BlockException;
import com.coincalf.framework.utils.DateUtil;
import com.coincalf.framework.utils.HttpUtil;
import com.coincalf.wallet.blockchain.BtcService;
import com.coincalf.wallet.blockchain.EthService;
import com.coincalf.wallet.blockchain.bean.TransInfo;
import com.coincalf.wallet.service.CoinWithdrawService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * @Description:
 * @Author: Chen Long
 * @Date: Created in 2018/5/21 下午9:24
 * @Modified by: Chen Long
 */
@Slf4j
@Component
@EnableScheduling
@Transactional(rollbackFor = Exception.class)
public class CoinWithdrawTask {

    @Autowired
    private CoinWithdrawService coinWithdrawService;
    @Autowired
    private CoinServiceClient coinServiceClient;
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    //ETH提币转账地址
    @Value("${eth.withdraw.address}")
    private String ethWithdrawAddress;
    //ETH提币转账地址私钥 AES加密
    @Value("${eth.withdraw.address.prk}")
    private String ethWithdrawAddressPrk;

    //BTC提币转账地址
    @Value("${btc.withdraw.address}")
    private String btcWithdrawAddress;
    //BTC提币转账地址私钥 AES加密
    @Value("${btc.withdraw.address.prk}")
    private String btcWithdrawAddressPrk;

    //EOS提币转账地址
    @Value("${eos.withdraw.address}")
    private String eosWithdrawAddress;
    //EOS提币转账地址私钥 AES加密
    @Value("${eos.withdraw.address.prk}")
    private String eosWithdrawAddressPrk;


    /**
     * 提币审核通过 执行转币 ETH 1分钟执行一次
     */
    @Scheduled(fixedRate = 60000)
    public void sendEthCoin() {
        log.info("执行ETH提币转账任务 当前时间={}", DateUtil.format(new Date(), "yyyy-MM-dd HH:mm:ss"));
        synchronized (CoinWithdrawTask.class) {
            this.sendCoin("ETH", "", this.ethWithdrawAddress, this.ethWithdrawAddressPrk);
        }
    }

    /**
     * 提币审核通过 执行转币 USDT_ERC20 1分钟执行一次
     */
    @Scheduled(fixedRate = 60000)
    public void sendUsdtErc20Coin() {
        log.info("执行USDT_ERC20提币转账任务 当前时间={}", DateUtil.format(new Date(), "yyyy-MM-dd HH:mm:ss"));
        synchronized (CoinWithdrawTask.class) {
            this.sendCoin("USDT_ERC20", "0xdac17f958d2ee523a2206206994597c13d831ec7", this.ethWithdrawAddress, this.ethWithdrawAddressPrk);
        }
    }

    /**
     * 提币审核通过 执行转币 BTC 5分钟执行一次
     */
    @Scheduled(fixedRate = 1000 * 300)
    public void sendBtcCoin() {
        log.info("执行BTC提币转账任务 当前时间={}", DateUtil.format(new Date(), "yyyy-MM-dd HH:mm:ss"));
        synchronized (CoinWithdrawTask.class) {
            this.sendCoin("BTC", "", this.btcWithdrawAddress, this.btcWithdrawAddressPrk);
        }
    }

    /**
     * 提币审核通过 执行转币 USDT_OMNI 5分钟执行一次
     */
    @Scheduled(fixedRate = 1000 * 300)
    public void sendUsdtOmniCoin() {
        log.info("执行USDT_OMNI提币转账任务 当前时间={}", DateUtil.format(new Date(), "yyyy-MM-dd HH:mm:ss"));
        synchronized (CoinWithdrawTask.class) {
            this.sendCoin("USDT_OMNI", "", this.btcWithdrawAddress, this.btcWithdrawAddressPrk);
        }
    }

    /**
     * 提币审核通过 执行转币 EOS 1分钟执行一次
     */
    @Scheduled(fixedRate = 60000)
    public void sendEosCoin() {
        log.info("执行EOS提币转账任务 当前时间={}", DateUtil.format(new Date(), "yyyy-MM-dd HH:mm:ss"));
        synchronized (CoinWithdrawTask.class) {
            this.sendCoin("EOS", "", this.eosWithdrawAddress, this.eosWithdrawAddressPrk);
        }
    }

    // TODO: 2021/2/5 链上转账成功 交易状态修改为通过
    @Scheduled(fixedRate = 1000 * 60)
    public void syncEthWithdrawTxStatus() {
        List<CoinWithdrawDTO> eth = coinWithdrawService.querySendCoiningByCoinName("ETH");
        List<CoinWithdrawDTO> usdtErc20 = coinWithdrawService.querySendCoiningByCoinName("USDT_ERC20");
        eth.addAll(usdtErc20);
        eth.forEach(w -> {
            TransInfo query = EthService.query(w.getTxid());
            WithdrawResultDTO withdrawResultDTO = new WithdrawResultDTO();
            withdrawResultDTO.setWithdrawCoinId(w.getId());
            if (query.getStatus().equals(TxStateEnum.SUCCESS.getStatus())) {
                withdrawResultDTO.setTxStatus(CoinWithdrawStatus.SUCCESS.getCode());
            } else if (query.getStatus().equals(TxStateEnum.FAIL.getStatus())) {
                //链上转账失败 交易状态修改为审核通过 重新执行打币流程
                withdrawResultDTO.setTxStatus(CoinWithdrawStatus.PASSED.getCode());
            }
            coinWithdrawService.updateTxStatus(withdrawResultDTO);
        });
    }

    @Scheduled(fixedRate = 1000 * 300)
    public void syncBtcWithdrawTxStatus() {
        List<CoinWithdrawDTO> btc = coinWithdrawService.querySendCoiningByCoinName("BTC");
        List<CoinWithdrawDTO> usdtOmni = coinWithdrawService.querySendCoiningByCoinName("USDT_OMNI");
        btc.addAll(usdtOmni);
        btc.forEach(w -> {
            TransInfo query = BtcService.query(w.getTxid());
            WithdrawResultDTO withdrawResultDTO = new WithdrawResultDTO();
            withdrawResultDTO.setWithdrawCoinId(w.getId());
            if (query.getStatus().equals(TxStateEnum.SUCCESS.getStatus())) {
                withdrawResultDTO.setTxStatus(CoinWithdrawStatus.SUCCESS.getCode());
            } else if (query.getStatus().equals(TxStateEnum.FAIL.getStatus())) {
                //链上转账失败 交易状态修改为审核通过 重新执行打币流程
                withdrawResultDTO.setTxStatus(CoinWithdrawStatus.PASSED.getCode());
            }
            coinWithdrawService.updateTxStatus(withdrawResultDTO);
        });
    }


    private void sendCoin(String coinName, String contractAddress, String fromAddress, String fromAddressPrk) {
        coinWithdrawService.queryPassedRecordByCoinName(coinName).forEach(w -> {
            //获取 提币转账地址
            if (StringUtils.isEmpty(fromAddress) || StringUtils.isEmpty(fromAddressPrk)) {
                log.warn("暂未设置提币地址以及私钥信息 coinName={}", coinName);
                return;
            }
            //log.info("提币交易链上转账待确认 id={} txId={}", w.getId(), transInfo.getTxHash());
            //链上转账待确认 提币交易状态修改为打币中
            WithdrawResultDTO withdrawResultDTO = new WithdrawResultDTO();
            withdrawResultDTO.setTxStatus(5);
            withdrawResultDTO.setWithdrawCoinId(w.getId());
            boolean b = coinWithdrawService.updateTxStatus(withdrawResultDTO);
            if (b) {
                TransInfo transInfo = new TransInfo();
                transInfo.setFromAddress(fromAddress);
                transInfo.setPrivateKey(fromAddressPrk);
                transInfo.setToAddress(w.getAddress());
                transInfo.setAmount(w.getMum());
                transInfo.setFee(this.minerFee(w.getCoinName()));
                transInfo.setContractAddress(contractAddress);
                transInfo.setCoinName(coinName);
                transInfo = coinWithdrawService.sendCoin(transInfo);
                if (transInfo.getStatus().equals(TxStateEnum.TO_BE_CONFIRMED.getStatus())) {
                    withdrawResultDTO.setTxId(transInfo.getTxHash());
                    boolean result = coinWithdrawService.updateTxId(withdrawResultDTO);
                    if (result) {
                        log.info("提币交易链上转账待确认 id={} txId={}", w.getId(), transInfo.getTxHash());
                    } else {
                        log.warn("提币交易链上转账失败 id={}", w.getId());
                        throw new BlockException("提币转账交易失败");
                    }
                } else if (transInfo.getStatus().equals(TxStateEnum.FAIL.getStatus())) {
                    //TODO 链上转账失败 继续执行...
                    log.info("提币交易链上转账失败，请人工及时处理 id={}  e={}", w.getId(), transInfo.getErrMsg());
                    throw new BlockException("提币转账交易失败");
                }
            }
        });
    }

    //获取矿工费
    private BigDecimal minerFee(String coinName) {
        CoinInfoDto coinInfoDto = coinServiceClient.selectCoinInfo(coinName);
        BigDecimal minerFee = BigDecimal.ZERO;
        //TODO 这里写死3 理应数据库获取
        if (coinInfoDto.getMainCoinId() == 3) {
            minerFee = this.getGasPriceByApi();
        } else if (coinInfoDto.getMainCoinId() == 1) {
            minerFee = this.getBtcFeeRate();
        }
        return minerFee;
    }

    /**
     * 获取当前最佳手续费（调用第三方API）
     *
     * @return gasPrice 单位:GWEI
     */
    public BigDecimal getGasPriceByApi() {
        String redisKey = "fee:miner:eth";
        BigDecimal gasPrice = (BigDecimal) redisTemplate.opsForValue().get(redisKey);
        if (null != gasPrice) {
            return gasPrice;
        }
        log.info("调用API获取gasPrice start...");
        String url = "https://ethgasstation.info/api/ethgasAPI.json?api-key=db40732a55392aa9fac5ec4f97e7363ea0c204dcc60b1285349037a4e660";
        String result;
        try {
            result = HttpUtil.doGet(url);
            log.info("调用API获取gasPrice end...");
        } catch (Exception e) {
            e.printStackTrace();
            throw new BlockException("调用API获取gsaPrice异常");
        }
        //平均手续费
        //return JSONObject.parseObject(result).getBigInteger("average");
        //快速打包手续费
        BigDecimal fastGasPrice = JSONObject.parseObject(result).getBigDecimal("fast");//fast
        fastGasPrice = fastGasPrice.divide(BigDecimal.valueOf(10L), 2, RoundingMode.HALF_UP);
        //缓存三分钟
        redisTemplate.opsForValue().set(redisKey, fastGasPrice, 3L, TimeUnit.MINUTES);
        return fastGasPrice;
        //最快打包手续费
        //return JSONObject.parseObject(result).getBigInteger("fastest");
    }

    /**
     * 获取btc费率
     *
     * @return
     */
    public BigDecimal getBtcFeeRate() {
        String redisKey = "fee:miner:btc";
        BigDecimal minerFee = (BigDecimal) redisTemplate.opsForValue().get(redisKey);
        if (null != minerFee) {
            return minerFee;
        }
        try {
            String httpGet1 = HttpUtil.doGet("https://bitcoinfees.earn.com/api/v1/fees/recommended");
            Map map = JSON.parseObject(httpGet1, Map.class);
            long fastestFee = Long.parseLong(map.get("fastestFee").toString());
            minerFee = BigDecimal.valueOf(fastestFee);
            redisTemplate.opsForValue().set(redisKey, minerFee, 5L, TimeUnit.MINUTES);
            return minerFee;
        } catch (Exception e) {
            log.warn("调用API获取BTC转账费率失败 e={}", e.getMessage());
            return null;
        }
    }

}
